﻿/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NTasking.Internals;

namespace NTasking
{

    /// <summary>
    /// Define the base class for all attributes used to mark Task entries and protected
    /// object's procedure and functions
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public abstract partial class SynchronizableAttribute : Attribute
    {
        /// <summary>
        /// Store information about the types which can be used for entry/procedure/function declarations
        /// </summary>
        private class SupportedType
        {

            /// <summary>
            /// Define the type ex the property which can be used for entry/proc/func declaration
            /// </summary>
            internal readonly Type MethodType;

            /// <summary>
            /// Define the associated type used to implement the entry/proc/func functionality
            /// </summary>
            internal readonly Type UnderlyingType;

            /// <summary>
            /// Define whether the type can be used in read-only declarations (i.e. protected functions)
            /// </summary>
            internal readonly bool CanBeReadOnly;


            /// <summary>
            /// Define whether the type can be used to declare entry families
            /// </summary>
            internal readonly bool IsFamily;


            /// <summary>
            /// Initializes a new instance of the <see cref="SupportedType"/> class.
            /// </summary>
            /// <param name="methodType">The key.</param>
            /// <param name="underlyingType">The value.</param>
            /// <param name="canBeReadOnly">if set to <c>true</c> [can be read only].</param>
            /// <param name="isFamily">if set to <c>true</c> [is family].</param>
            internal SupportedType(Type methodType, Type underlyingType, bool canBeReadOnly, bool isFamily)
            {
                MethodType = methodType;
                UnderlyingType = underlyingType;
                CanBeReadOnly = canBeReadOnly;
                IsFamily = isFamily;
            }

        }

        /// <summary>
        /// Encapsulate the necessary data and logic to initialize a given synchronized property.
        /// </summary>
        private class PropertyInitializer
        {

            /// <summary>
            /// The property to be initialized
            /// </summary>
            private readonly PropertyInfo _property;

            /// <summary>
            /// Whether the property is an entry (<c>true</c>) or a protected operation (<c>false</c>)
            /// </summary>
            private readonly bool _isEntry;

            /// <summary>
            /// Whether the property is a read-only synchronized operation (i.e. a protected function)
            /// </summary>
            private readonly bool _isReadOnly;

            /// <summary>
            /// A Constructor used to build the property's underlying type
            /// </summary>
            private readonly ConstructorInfo _constructor;

            /// <summary>
            /// A method used to retrieve the property value from the property's underlying type
            /// </summary>
            private readonly MethodInfo _getValue;


            /// <summary>
            /// Initializes a new instance of the <see cref="PropertyInitializer"/> class.
            /// </summary>
            /// <param name="isEntry">if set to <c>true</c> the property is an Entry.</param>
            /// <param name="isReadonly">if set to <c>true</c> the property is a protected function.</param>
            /// <param name="property">The property.</param>
            /// <param name="constructor">The constructor used to build the property's underlying value.</param>
            /// <param name="getValue">
            /// A method used to retrieve the property value if it is different from the property's underlying type
            /// </param>
            internal PropertyInitializer(bool isEntry, bool isReadonly, PropertyInfo property, ConstructorInfo constructor, MethodInfo getValue)
            {
                _property = property;
                _isEntry = isEntry;
                _isReadOnly = isReadonly;
                _constructor = constructor;
                _getValue = getValue;
            }


            /// <summary>
            /// Applies this <see cref="PropertyInitializer"/> to the given <see cref="SynchronizedObject"/>.
            /// </summary>
            /// <param name="owner">The <see cref="SynchronizedObject"/> to initialize.</param>
// ReSharper disable MemberHidesStaticFromOuterClass
            internal void Initialize(SynchronizedObject owner)
// ReSharper restore MemberHidesStaticFromOuterClass
            {
                // Create the property's underlying type
                SynchronizedMethod synchPoint = (SynchronizedMethod)_constructor.Invoke(NoParameters);

                synchPoint.Owner = owner;
                synchPoint.IsEntry = _isEntry;
                synchPoint.IsReadOnly = _isReadOnly;
                synchPoint.Name = _property.Name;

                // Add it to the list of synchronization points of the synchronization object beeing initialized
                owner.SynchronizedMethods.Add(synchPoint);

                // Set the value of the corresponding property
                object propertyValue = _getValue == null ? synchPoint : _getValue.Invoke(synchPoint, NoParameters);

                _property.SetValue(owner, propertyValue, null);
            }

        }


        /// <summary>
        /// Encapsulate the necessary data and logic to initialize all synchronized properties of a given type.
        /// </summary>
        private class SynchronizationObjectInitializer
        {

            /// <summary>
            /// The list of all properties of the type 
            /// </summary>
            private readonly PropertyInitializer[] _properties;

            /// <summary>
            /// A method used to retrieve an exception indicating that the type definition is incorrect
            /// </summary>
            private readonly Func<PropertyInfo, Exception> _definitionError;

            /// <summary>
            /// The name of the property which definition is erroneous in the type
            /// </summary>
            private readonly PropertyInfo _propertyInError;


            /// <summary>
            /// Initializes a new instance of the <see cref="SynchronizationObjectInitializer"/> class.
            /// </summary>
            /// <param name="definitionError">The method used to retrieve an exception indicating that the type definition is incorrect.</param>
            /// <param name="propertyInError">The name of the property which definition is erroneous.</param>
            internal SynchronizationObjectInitializer(Func<PropertyInfo, Exception> definitionError, PropertyInfo propertyInError)
            {
                _properties = null;
                _definitionError = definitionError;
                _propertyInError = propertyInError;
            }


            /// <summary>
            /// Initializes a new instance of the <see cref="SynchronizationObjectInitializer"/> struct.
            /// </summary>
            /// <param name="properties">The list of synchronized properties belonging to the type.</param>
            internal SynchronizationObjectInitializer(PropertyInitializer[] properties)
            {
                _properties = properties;
                _definitionError = null;
                _propertyInError = null;
            }


            /// <summary>
            /// Applies this <see cref="SynchronizationObjectInitializer"/> to the given <see cref="SynchronizedObject"/>.
            /// </summary>
            /// <param name="owner">The <see cref="SynchronizedObject"/> to initialize.</param>
// ReSharper disable MemberHidesStaticFromOuterClass
            internal void Initialize(SynchronizedObject owner)
// ReSharper restore MemberHidesStaticFromOuterClass
            {
                if (_definitionError != null)
                    throw _definitionError(_propertyInError);

                foreach (PropertyInitializer property in _properties)
                    property.Initialize(owner);
            }

        }


        /// <summary>
        /// Constant used to represent an empty parameter profile
        /// </summary>
        private static readonly Type[] _parameterless = new Type[0];

        /// <summary>
        /// Constant used to represent an empty parameter list
        /// </summary>
        internal static readonly object[] NoParameters = new object[0];


        /// <summary>
        /// Gets a value indicating whether this instance applies to an entry or a protected method.
        /// </summary>
        /// <value><c>true</c> if this instance applies to an entry; otherwise, <c>false</c>.</value>
        protected abstract bool IsEntry { get; }


        /// <summary>
        /// Gets a value indicating whether this instance applies to a read-only method.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance applies to a read-only method; otherwise, <c>false</c>.
        /// </value>
        protected abstract bool IsReadOnly { get; }


        /// <summary>
        /// Maintains a cache of all types for which an initializer has already been built
        /// </summary>
        private static readonly Dictionary<Type, SynchronizationObjectInitializer> _cache = new Dictionary<Type, SynchronizationObjectInitializer>();


        /// <summary>
        /// Initializes the specified <see cref="SynchronizedObject"/>.
        /// </summary>
        /// <param name="owner">The <see cref="SynchronizedObject"/> to initialize.</param>
        internal static void Initialize(SynchronizedObject owner)
        {
            GetInitializer(owner).Initialize(owner);
        }


        /// <summary>
        /// Retrieve the initializer for the given <see cref="SynchronizedObject"/> by first looking
        /// into the cached initializers, and building one if no intializer is in the cache.
        /// </summary>
        /// <param name="owner">The <see cref="SynchronizedObject"/> for which the initializer is requested.</param>
        /// <returns>The requested initializer</returns>
        private static SynchronizationObjectInitializer GetInitializer(SynchronizedObject owner)
        {
            Type type = owner.GetType();
            SynchronizationObjectInitializer result;

            lock (_cache)
            {
                if (!_cache.TryGetValue(type, out result))
                {
                    result = BuildInitializer(owner);
                    _cache[type] = result;
                }
            }

            return result;
        }


        /// <summary>
        /// Builds a new initializer for the given <see cref="SynchronizedObject"/>.
        /// </summary>
        /// <param name="owner">The <see cref="SynchronizedObject"/> for which the initializer is requested</param>
        /// <returns>The newly built initializer</returns>
        private static SynchronizationObjectInitializer BuildInitializer(SynchronizedObject owner)
        {
            SynchronizableAttribute[] attributes;
            List<PropertyInitializer> properties = new List<PropertyInitializer>();
            SynchronizableAttribute attribute;
            Type propertyType;
            Type synchronizableType;
            MethodInfo setter;
            Type type = owner.GetType();

            // Loop starts at the owner's type and goes all through the class hierarchy
            while (type != null)
            {
                // Loops goes over all the type's defined properties
                foreach (PropertyInfo property in type.GetProperties(BindingFlags.Instance |
                                                                     BindingFlags.Public |
                                                                     BindingFlags.NonPublic |
                                                                     BindingFlags.DeclaredOnly))
                {
                    // We look for Synchronizable attributes of the property
                    attributes = GetAttributes(type, property);

                    if (attributes.Length == 0) continue;

                    // We check that the property does not have conflicting attribute definition between
                    // the property declaration in the type itself and en eventual property declaration in
                    // an implemented interface
                    if (HasConflictingAttributes(attributes))
                        return new SynchronizationObjectInitializer(Errors.ConflictingAttribute, property);

                    // Check that the property has a private setter
                    setter = property.GetSetMethod(true);

                    if (setter == null)
                        return new SynchronizationObjectInitializer(Errors.NoPropertySetter, property);
                    if (!setter.IsPrivate)
                        return new SynchronizationObjectInitializer(Errors.PropertySetterIsNotPrivate, property);

                    attribute = attributes[0];

                    // Check that if type is a task type, only entry attributes can be used
                    if (attribute.GetType() != typeof (EntryAttribute) && owner.IsThreaded)
                        return new SynchronizationObjectInitializer(Errors.ProtectedOperationNotAllowedInTask, property);

                    // Check that the propertiy's type is one of the types supported by the runtime

                    propertyType = property.PropertyType;
                    synchronizableType = null;
                    bool isFamily = false;

                    if (propertyType.IsGenericType)
                        propertyType = propertyType.GetGenericTypeDefinition();

                    foreach (SupportedType supportedType in _supportedTypes)
                    {
                        if (propertyType != supportedType.MethodType)
                            continue;

                        // Check that the supported type can be read-only if the attribute implies a read-only method
                        if (attribute.IsReadOnly && !supportedType.CanBeReadOnly)
                            return new SynchronizationObjectInitializer(Errors.InvalidReadOnlyMethodType, property);

                        // Check that type is a task type if the attribute implies an entry family
                        if (supportedType.IsFamily && !attribute.IsEntry)
                            return new SynchronizationObjectInitializer(Errors.ProtectedOperationNotAllowedForFamily, property);

                        // At this point we have found a type which is supported. The only remaining thing to
                        // do is that, if this supported type is generic, we need to instantiate it with the
                        // proper generic arguments

                        synchronizableType = supportedType.UnderlyingType;

                        if (property.PropertyType.IsGenericType)
                            synchronizableType =
                                synchronizableType.MakeGenericType(property.PropertyType.GetGenericArguments());

                        isFamily = supportedType.IsFamily;

                        break;
                    }

                    // the type of the property is not supported by the runtime
                    if (synchronizableType == null)
                        return new SynchronizationObjectInitializer(Errors.UnsupportedSynchronizedMethodType, property);

                    // get a constructor used to build an instance of this supported type
                    ConstructorInfo constructor = synchronizableType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
                                                                                    null, _parameterless, null);

                    // get the method used to convert from the iunderlying type to the property type
                    MethodInfo method = isFamily ? null : synchronizableType.GetMethod("SynchronousCall", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

                    // Create the new property initializer
                    properties.Add(new PropertyInitializer(attribute.IsEntry, attribute.IsReadOnly, property, constructor,
                                                    method));
                }

                // Go to the base type until we have reached the top of the inheritance chain
                type = type.BaseType;
            }

            // Create a new initializer with all associated property initializers
            return new SynchronizationObjectInitializer(properties.ToArray());
        }


        /// <summary>
        /// Gets all the <see cref="SynchronizableAttribute"/> attributes attached to a given
        /// property of a given type. The list of attributes will contain all the attributes
        /// appearing in the class declaration and in any implemented interface declaration.
        /// </summary>
        /// <param name="type">The type for which the attributes must be retrieved.</param>
        /// <param name="property">The property for which the attributes must be retrieved.</param>
        /// <returns>The list of attributes attached to the property</returns>
        private static SynchronizableAttribute[] GetAttributes(Type type, PropertyInfo property)
        {
            List<SynchronizableAttribute> attributes =
                property.GetCustomAttributes(typeof(SynchronizableAttribute), true).Cast<SynchronizableAttribute>().ToList();

            foreach (Type implemented in type.GetInterfaces())
            {
                PropertyInfo ifProperty = implemented.GetProperty(property.Name);

                if (ifProperty == null) continue;

                attributes.AddRange(ifProperty.GetCustomAttributes(typeof(SynchronizableAttribute), true).Cast<SynchronizableAttribute>());
            }

            return attributes.ToArray();
        }


        /// <summary>
        /// Determines whether a list of attributes attached to a synchronizable property
        /// has conflicting declarations: i.e. declared in a class or interface as Entry and declared
        /// in a derived class as ReadOnlyProtectedOperation.
        /// </summary>
        /// <param name="attributes">The list of attributes attached to a synchronizable property.</param>
        /// <returns>
        /// 	<c>true</c> if the attributes contain conflicting declarations, <c>false</c> otherwise.
        /// </returns>
        private static bool HasConflictingAttributes(IList<SynchronizableAttribute> attributes)
        {
            for (int i = 1; i < attributes.Count; i++)
                if (attributes[0].GetType() != attributes[i].GetType())
                    return true;

            return false;
        }

    }


    /// <summary>
    /// An attribute used to mark entry properties.
    /// </summary>
    public class EntryAttribute : SynchronizableAttribute
    {

        /// <summary>
        /// Gets a value indicating whether this instance applies to an entry or a protected method.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance applies to an entry; otherwise, <c>false</c>.
        /// </value>
        protected override bool IsEntry
        {
            get { return true; }
        }


        /// <summary>
        /// Gets a value indicating whether this instance applies to a read-only method.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance applies to a read-only method; otherwise, <c>false</c>.
        /// </value>
        protected override bool IsReadOnly
        {
            get { return false; }
        }
    }


    /// <summary>
    /// An attribute used to mark read-write protected operations
    /// </summary>
    public class ReadWriteOperationAttribute : SynchronizableAttribute
    {

        /// <summary>
        /// Gets a value indicating whether this instance applies to an entry or a protected method.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance applies to an entry; otherwise, <c>false</c>.
        /// </value>
        protected override bool IsEntry
        {
            get { return false; }
        }


        /// <summary>
        /// Gets a value indicating whether this instance applies to a read-only method.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance applies to a read-only method; otherwise, <c>false</c>.
        /// </value>
        protected override bool IsReadOnly
        {
            get { return false; }
        }
    }


    /// <summary>
    /// An attribute used to mark read-only protected operations
    /// </summary>
    public class ReadOnlyOperationAttribute : SynchronizableAttribute
    {

        /// <summary>
        /// Gets a value indicating whether this instance applies to an entry or a protected method.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance applies to an entry; otherwise, <c>false</c>.
        /// </value>
        protected override bool IsEntry
        {
            get { return false; }
        }


        /// <summary>
        /// Gets a value indicating whether this instance applies to a read-only method.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance applies to a read-only method; otherwise, <c>false</c>.
        /// </value>
        protected override bool IsReadOnly
        {
            get { return true; }
        }
    }

}
